﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Drawing;
namespace SuDokuSolution
{

    /// Class for generating numbers

    public class NumberGenerator : INumberGenerator
    {

        internal int[,] completeGrid;
        internal int[,] problemGrid;
        internal   int n;
        internal int m;
        internal Constants one;
        private int level;


        public NumberGenerator()
        {

        }
        

        public int[] arr = new int[4] { 0, 1, 2, 3 };
        public int[] arr1 = new int[9] { 0, 1, 2, 3, 4, 5, 6, 7, 8 };

     
        /// method for generating complete grid
        
        public bool CreatCompleteGrid()
        {
            for (int row = 0; row < n; row++)
            {
                for (int col = 0; col < n; col++)
                {
                    this.problemGrid[row, col] = one.NoNumber;
                    this.completeGrid[row, col] = one.NoNumber;
                }
            }

            ArrayList freeCols = new ArrayList(n);
            ArrayList filledPoints = new ArrayList(n); 			//for storing positions of filled ponts so that making roll back easy          

            Random rand = new Random();
            for (int count = 1; count <= n; count++)
            {
                int repetitions = 0;
                filledPoints.Clear();
                for (int row = 0; row < n; row += 2)
                {
                    //rows are being filled at a diff of two, at the end it rolls off to start
                    repetitions++;
                    if (repetitions == 150)
                    {
                        return false;
                    }
                    freeCols.Clear();
                    //
                    if (n == 4)
                    {
                        foreach (int number in arr)
                        {
                            freeCols.Add(number);
                        }
                    }
                    //

                    //
                    if(n==9)
                    {
                        foreach (int number in arr1)
                        {
                            freeCols.Add(number);
                        }
                    }
                    //
                    int col = 0;
                    bool repeat = true;
                    while (repeat) //find a position in current row for placing current count
                    {
                        repeat = false;
                        if (freeCols.Count == 0)
                        {
                            //remove no. filled already
                            for (int rollBack = 0; rollBack < filledPoints.Count; rollBack++)
                            {
                                Point p = (Point)filledPoints[rollBack];
                                completeGrid[p.X, p.Y] = one.NoNumber;
                            }
                            row = -2;
                            repeat = true;
                            break;
                        }

                        //select a new column at random and get its value                        
                        col = rand.Next(0, freeCols.Count);
                        col = (int)freeCols[col];
                        if (this.completeGrid[row, col] != one.NoNumber)
                        {
                            freeCols.Remove(col);
                            repeat = true;
                            continue;
                        }

                        int HB = (int)(row / m);
                        int VB = (int)(col / m);

                        for (int i = 0; i < n; i++)
                        {
                            if (completeGrid[i, col] == count)
                            {
                                freeCols.Remove(col);
                                repeat = true;
                            }
                        }

                        if (repeat)
                            continue;

                        for (int i = HB * m; i < (HB + 1) * m; i++)
                        {
                            for (int j = VB * m; j < (VB + 1) * m; j++)
                            {
                                if (completeGrid[i, j] == count)
                                {
                                    freeCols.Remove(col);
                                    repeat = true;
                                }
                            }
                        }
                    }

                    //if while loop is broken and repeat is true than start from row zero again
                    if (repeat)
                        continue;

                    this.completeGrid[row, col] = count;
                    filledPoints.Add(new Point(row, col));
                    this.problemGrid[row, col] = this.completeGrid[row, col];
                    
                    if ((n==4&&row == 2)||(n==9&&row==8))
                        row = -1;
                }
            }
            return true;
        }
       
        /// for generating probem grid
        
        public bool CreatProblemGrid()
        {
            Random rand = new Random();
            int[,] solArray = new int[one.nbOfCells, one.nbOfCells];

            ArrayList points = new ArrayList();
            for (int row = 0; row < one.nbOfCells; row++)
            {
                for (int col = 0; col < one.nbOfCells; col++)
                {
                    points.Add(new Point(row, col));
                }
            }

            for (int count = 0; count < this.level; count++)
            {
                if (points.Count == 0)
                    return false;

                int pRand = rand.Next(0, points.Count);
                Point p = (Point)points[pRand];
                int row = p.X;
                int col = p.Y;

                this.problemGrid[row, col] = one.NoNumber;
                if (!(HasSolution(problemGrid, ref solArray) == true
                    && AreEqualArray(solArray, completeGrid) == true))
                {
                    this.problemGrid[row, col] = this.completeGrid[row, col];
                    count--;
                    points.RemoveAt(pRand);
                    continue;
                }
                points.RemoveAt(pRand);


            }
            return true;
        }


      
        /// Checks if the problem grid passed is solvable
       
        public bool HasSolution(int[,] underProcessingGrid, ref int[,] returnArray)
        {
            CellData[,] solution = new CellData[one.nbOfCells, one.nbOfCells];
            for (int row = 0; row < one.nbOfCells; row++)
            {
                for (int col = 0; col < one.nbOfCells; col++)
                {
                    solution[row, col] = new CellData();
                    solution[row, col].candidateNb.Clear();
                    solution[row, col].originalNb = underProcessingGrid[row, col];
                }
            }

            #region search for candidate nbs and_ set the candidate nb list of each unfilled cell
            for (int row = 0; row < one.nbOfCells; row++)
            {
                for (int col = 0; col < one.nbOfCells; col++)
                {
                    if (solution[row, col].originalNb != one.NoNumber)
                        continue;
                    int HB = (int)(row / one.nbOfLines);
                    int VB = (int)(col / one.nbOfLines);
                    ArrayList candidateNb = new ArrayList(n);
                    if (n == 4)
                    {
                        foreach (int number in arr)
                        {
                            candidateNb.Add(number);

                        }
                    }
                    //

                    //
                    if (n == 9)
                    {
                        foreach (int number in arr1)
                        {
                            candidateNb.Add(number);

                        }
                    }
                  

                    for (int i = 0; i < n; i++)
                    {
                        candidateNb.Remove(solution[row, i].originalNb);
                        candidateNb.Remove(solution[i, col].originalNb);
                    }

                    for (int i = HB * m; i < (HB + 1) * m; i++)
                    {
                        for (int j = VB * m; j < (VB + 1) * m; j++)
                        {
                            candidateNb.Remove(solution[i, j].originalNb);
                        }
                    }
                    for (int i = 0; i < candidateNb.Count; i++)
                        solution[row, col].candidateNb.Add(candidateNb[i]);
                }
            }
            #endregion

            bool complete = false;
            bool canSolve = true;
            while (!complete)
            {
                if (!canSolve)
                    return false;
                complete = true;
                canSolve = false;
                for (int row = 0; row < one.nbOfCells; row++)
                {
                    for (int col = 0; col < one.nbOfCells; col++)
                    {
                        if (solution[row, col].originalNb == one.NoNumber)
                        {
                            if (complete)
                                complete = false;
                        }
                        else
                            continue;
                        int HB = (int)(row / one.nbOfLines);
                        int VB = (int)(col / one.nbOfLines);
                        if (solution[row, col].candidateNb.Count == 1)
                        {
                            canSolve = true;
                            int nb = solution[row, col].originalNb = (int)solution[row, col].candidateNb[0];
                            solution[row, col].candidateNb.Clear();
                            //now modify other cell's  candidates
                            for (int i = 0; i < one.nbOfCells; i++)
                            {
                                solution[row, i].candidateNb.Remove(nb);
                                solution[i, col].candidateNb.Remove(nb);
                            }

                            for (int i = HB * m; i < (HB + 1) * m; i++)
                            {
                                for (int j = VB * m; j < (VB + 1) * m; j++)
                                {
                                    solution[i, j].candidateNb.Remove(nb);
                                }
                            }
                        }

                        else  //find nb that cannot come into any cell of that block
                        {
                            for (int index = 0; index < solution[row, col].candidateNb.Count; index++)
                            {
                                int number = (int)solution[row, col].candidateNb[index];
                                bool found = false;
                                for (int i = HB * one.nbOfLines; i < (HB + 1) * one.nbOfLines; i++)
                                    for (int j = VB * one.nbOfLines; j < (VB + 1) * one.nbOfLines; j++)
                                        if (solution[i, j].candidateNb.Contains(number) && !(i == row && j == col))
                                        {
                                            j = (VB + 1) * one.nbOfLines; //exit from loop
                                            i = (HB + 1) * one.nbOfLines;
                                            found = true;
                                        }

                                if (!found)
                                {
                                    //the candidate  nb is found
                                    solution[row, col].originalNb = number;
                                    canSolve = true;
                                    solution[row, col].candidateNb.Clear();
                                    for (int i = 0; i < n; i++) //remove that num from other lists
                                    {
                                        solution[row, i].candidateNb.Remove(number);
                                        solution[i, col].candidateNb.Remove(number);
                                    }
                                    index = 10; //exit from loop
                                }
                            }
                        }
                    }
                }
            }


            for (int row = 0; row < n; row++)
            {
                for (int col = 0; col < n; col++)
                {
                    returnArray[row, col] = solution[row, col].originalNb;
                    solution[row, col].Dispose(true);
                }
            }
            return true;
        }


    
        /// Checks if the two array passed are equal
        
        public bool AreEqualArray(int[,] first, int[,] second)
        {
            if (first.Length != second.Length)
                return false;

            for (int row = 0; row < one.nbOfCells; row++)
            {
                for (int col = 0; col < one.nbOfCells; col++)
                {
                    if (first[row, col] != second[row, col])
                    {
                        //MessageBox.Show("Method areequal returning false ");
                        return false;
                    }
                }
            }
            return true;
        }
        public bool GenerateProblem()
        {

           // mainForm.Enabled = false;
            try
            {
                using (SuDokuSolution.difficultyDialog dd = new SuDokuSolution.difficultyDialog())
                {
                    dd.ShowDialog();
                    this.level = dd.Level;
                    if (this.level == 0 || this.level >= (one.nbOfLines * one.nbOfLines * one.nbOfCells - 3))
                    {
                        //MessageBox.Show("Error");
                        return false;
                    }
                }






                while (!this.CreatCompleteGrid())
                    System.GC.Collect();

                while (!this.CreatProblemGrid())
                    problemGrid = (int[,])completeGrid.Clone();


            }
            catch (Exception ex)
            {
               // MessageBox.Show("Some error has occured to the application"
                    //+ ",Retry or close the program and start again\n" + ex.Message, "Error");
            }
            finally
            {

                //mainForm.TopMost = true;
                //mainForm.TopMost = false;

                System.GC.Collect();
                //mainForm.Enabled = true;
            }
            return true;


        }
    }
}
